// newlib includes
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/dirent.h>

// whether we can fork or not
#define CANFORK 0 // NO!

// entry point
int main( int argc, char* argv[] )
{
	// set stdin as unbuffered
	setvbuf( stdin, 0, _IONBF, 0 );
	setvbuf( stdout, 0, _IONBF, 0 );
	
	// print out some startup text
	printf( "MiniSH: The Mattise Startup Shell\nCopyright 2008 Matthew Iselin\n\n\nType 'help' then hit enter for assistance.\n\n" );
	
	// the input buffer (all input goes here)
	char* inbuff = (char*) malloc( 1024 );
	
	// the previous command buffer
	char** prevcommand = (char**) malloc( sizeof( char* ) * 1024 );
	memset( prevcommand, 0, sizeof( char* ) * 1024 );
	
	// the current directory - starts out at the root level
	char* currdir = (char*) malloc( 1024 );
	strcpy( currdir, "/hd0/" );
	asm volatile( "int $0x80" : : "a" (18), "S" ((unsigned int) currdir) );
	
	// the previous command index
	int pcmdindx = -1;
	
	// input buffer index
	int indx = 0;
	
	// main loop: reads input, parses input, handles input, performs actions
	while( 1 )
	{
		// print the prompt
		printf( "\nuser@term\n%s $ ", currdir );
		
		// keep reading characters until eof or a newline
		indx = 0;
		int currlen = 0; // when we go left/right, this updates
		unsigned char c = 'a';
		while( indx < 1024 )
		{
			c = getchar();
			
			// check for special characters
			if( c == 8 )
			{
				// check that we can go back
				if( ! ( indx - 1 < 0 ) )
				{
					// write the backspace, clear the last character written and then go back another character
					putchar( c );
					putchar( ' ' );
					putchar( 8 );
					
					// decrement the index and null out that character
					inbuff[indx--] = 0;
				}
				
				// continue the loop
				continue;
			}
			else if( c == (75 | 0x80) )
			{
				// left arrow much?
				if( ! ( indx - 1 < 0 ) )
				{
					putchar( 8 );
					indx--;
				}
				continue;
			}
			else if( c == (77 | 0x80) )
			{
				// right arrow much?
				if( ! ( indx + 1 > currlen ) )
				{
					putchar( ' ' );
					putchar( 8 );
					putchar( inbuff[indx] );
					indx++;
				}
				continue;
			}
			else if( c == (72 | 0x80) )
			{
				// firstly check that there are arguments to use
				if( pcmdindx > 0 )
				{
					// check the previous argument
					if( prevcommand[pcmdindx - 1] != 0 )
					{
						// decrement the previous command index
						pcmdindx--;
						
						// firstly clear the input string
						int i;
						for( i = 0; i < currlen; i++ )
						{
							putchar( 8 );
							putchar( ' ' );
							putchar( 8 );
							inbuff[i] = 0;
						}
						
						// and then write out the new one
						for( i = 0; i < strlen( prevcommand[pcmdindx] ); i++ )
						{
							inbuff[i] = prevcommand[pcmdindx][i];
							putchar( inbuff[i] );
						}
						
						// set the current index and the current length
						indx = currlen = i;
					}
				}
				continue;
			}
			else if( c == (80 | 0x80) )
			{
				// firstly check that there are arguments to use
				if( pcmdindx >= 0 && pcmdindx < 1024 )
				{
					// check the previous argument
					if( prevcommand[pcmdindx + 1] != 0 )
					{
						// increment the previous command index
						pcmdindx++;
						
						// firstly clear the input string
						int i;
						for( i = 0; i < currlen; i++ )
						{
							putchar( 8 );
							putchar( ' ' );
							putchar( 8 );
							inbuff[i] = 0;
						}
						
						// and then write out the new one
						for( i = 0; i < strlen( prevcommand[pcmdindx] ); i++ )
						{
							inbuff[i] = prevcommand[pcmdindx][i];
							putchar( inbuff[i] );
						}
						
						// set the current index and the current length
						indx = currlen = i;
					}
				}
				continue;
			}
			
			// print the character
			putchar( c );
			
			// check if we should stop
			if( c == '\n' )
				break;
			
			// put the character into the buffer
			inbuff[indx++] = c;
			
			// another byte added, but don't increment the string length unless we aren't in the middle of the string
			if( (indx - 1) == currlen )
				currlen++;
		}
		inbuff[currlen] = 0;
		
		// check to see if its a valid command
		if( currlen == 0 )
			continue;
		
		// save the command into the previous command buffer
		if( pcmdindx == -1 )
			pcmdindx = 0;
		prevcommand[pcmdindx] = (char*) malloc( (sizeof( char ) * currlen) + 1 );
		strcpy( prevcommand[pcmdindx], inbuff );
		
		// another command bytes the dust
		pcmdindx++;
		
		// count the number of spaces and build an argument array
		int argcount = 0, i, inquote = 0;
		for( i = 0; i < strlen( inbuff ); i++ )
		{			
			if( inbuff[i] == ' ' && inquote == 0 && inbuff[i-1] != '\\' )
				argcount++;
			
			// check for a quote
			if( inbuff[i] == '"' && inbuff[i-1] != '\\' )
				inquote = !inquote;
			
			// if the next character is the end of the string, increment the argument count (so that the last argument doesn't get skipped)
			if( inbuff[i+1] == 0 )
				argcount++;
		}
		
		// allocate space for the arguments
		char** arguments = (char**) malloc( sizeof( char* ) * (argcount + 1) );
		
		// also allocate room for an index buffer (this holds the offsets of each argument)
		int* indxbuff = (int*) malloc( sizeof( int ) * (argcount + 1) );
		
		// enter offsets into the index buffer
		int lastindx = 0;
		inquote = 0;
		int argnum = 0;
		int bufflen = strlen( inbuff ); // we will be putting nulls in, so the strlen won't come out right if it's in the loop
		for( i = 0; i < bufflen; i++ )
		{
			// a space can happen anywhere in the string
			if( inbuff[i] == ' ' && inquote == 0 && inbuff[i-1] != '\\' )
			{
				indxbuff[argnum++] = lastindx;
				inbuff[i] = 0;
				lastindx = i + 1;
			}
			
			// check for a quote
			if( inbuff[i] == '"' && inbuff[i-1] != '\\' )
			{
				if( inquote && inbuff[i+1] == ' ' )
					inbuff[i] = 0;
				else if( inquote )
				{
					indxbuff[argnum++] = lastindx;
					inbuff[i] = 0;
				}
				else
					lastindx = i + 1;
				inquote = !inquote;
			}
			
			// but the null byte at the end won't get handled properly without this
			if( inbuff[i+1] == 0 )
				indxbuff[argnum++] = lastindx;
		}
		
		// and finally grab each argument
		for( i = 0; i < argcount; i++ )
		{
			arguments[i] = (char*) malloc( strlen( (char*) &inbuff[indxbuff[i]] ) + 1 );
			strcpy( arguments[i], (char*) &inbuff[indxbuff[i]] );
		}
		
		// is the command a runnable program?
		int runnable = 0;
		
		// parse the command string
		if( arguments[0][0] != '/' )
		{
			// firstly find out if it's a known argument string
			if( strcmp( arguments[0], "cd" ) == 0 )
			{
				// change the directory to whatever the first argument is
				if( argcount == 2 )
				{
					// check to see if we need to prepend the current working directory
					if( arguments[1][0] != '/' )
					{
						char* newarg = (char*) malloc( strlen( arguments[1] ) + strlen( currdir ) + 1 );
						strcpy( newarg, currdir );
						if(currdir[strlen(currdir)-1] != '/')
							strcat( newarg, "/" );
						strcat( newarg, arguments[1] );
						free( arguments[1] );
						arguments[1] = newarg;
					}
				
					// does it exist? remember that open works in the current directory if the first char isn't a slash, so this works nicely
					DIR* d = opendir(arguments[1]);
					if( d )
					{
						strcpy( currdir, arguments[1] );
						asm volatile( "int $0x80" : : "a" (18), "S" ((unsigned int) currdir) );
						closedir( d );
					}
					else
						printf( "minish: couldn't switch directory: no such folder!\n" );
				}
				else
					printf( "minish: too many or too few arguments passed to cd\n" );
			}
			else if( strcmp( arguments[0], "echo" ) == 0 )
			{
				// if there is an argument, print it
				if( argcount >= 2 )
					printf( "%s\n", arguments[1] );
				else
					printf( "\n" ); // just print a newline
			}
			else if( strcmp( arguments[0], "help" ) == 0 )
			{
				// if there is an argument, someone wants help on a specific thing
				if( argcount >= 2 )
				{
					if( strcmp( arguments[1], "cd" ) == 0 )
						printf( "cd: changes the current working directory.\nusage: cd path\n" );
					else if( strcmp( arguments[1], "echo" ) == 0 )
						printf( "echo: outputs a string.\nusage: echo string\n" );
					else if( strcmp( arguments[1], "meminfo" ) == 0 )
						printf( "meminfo: shows information about memory in the system, no arguments\n" );
					else if( strcmp( arguments[1], "ls" ) == 0 )
						printf( "ls: prints the contents of a directory.\nusage: ls [dir]\n\ndir: optional, the directory to show contents of\n" );
					//else if( strcmp( arguments[1], "load" ) == 0 )
					//	printf( "load: loads a driver into RAM for usage later.\nusage: load path\n" );
				}
				else
				{
					// print out general help
					printf( "For more assistance with a specific command, type help [command].\n\n" );
					
					// output information about all the commands
					printf( "cd: changes current working directory\n" );
					printf( "echo: prints a string\n" );
					printf( "meminfo: prints memory information\n" );
					printf( "ls: prints the contents of a directory\n" );
					//printf( "load: loads a driver into RAM\n" );
				}
			}
			else if( strcmp( arguments[0], "ls" ) == 0 )
			{
				// print out all the files/folders in the given directory
				DIR* dir;
				if( argcount >= 2 )
					dir = opendir( arguments[1] );
				else
					dir = opendir( currdir );
				if( dir == 0 )
				{
					printf( "minish: couldn't find folder '%s'\n", (argcount >= 2) ? (arguments[1]) : (currdir) );
				}
				else
				{
					struct dirent* d = readdir( dir );
					while( d )
					{
						printf( "%s\n", d->d_name );
						d = readdir( dir );
					}
					closedir( dir );
				}
			}
			else if( strcmp( arguments[0], "meminfo" ) == 0 )
			{
				// find out the number of bytes free
				uint32_t byts_free;
				asm volatile( "int $0x80" : "=D" (byts_free) : "a" (19) );
				
				// do this via floats
				float free_gb = byts_free / (1024 * 1024 * 1024.0f);
				
				// print it out
				printf( "memory information:\nfree GB: %f\nfree MB: %d\nfree KB: %d\nfree BYTES: %d\n\n", free_gb, byts_free / 1024 / 1024, byts_free / 1024, byts_free );
			}
			/*else if( strcmp( arguments[0], "load" ) == 0 )
			{
				// only work if we do have an argument containing the driver to load
				if( argcount >= 2 )
				{
					// load the driver
					int edi;
					asm volatile( "int $0x80" : "=D" (edi) : "a" (17), "S" (arguments[1]) );
					printf( "minish: loading driver '%s': %s\n", arguments[1], (edi == -1) ? "failed" : "success" );
				}
			}*/
			else
			{
				// no, so prepend the current working directory and try to run the program - UNLESS it's an absolute directory
				if( arguments[0][0] != '/' )
				{
					size_t currdir_len = strlen(currdir);
					char* newarg = (char*) malloc( strlen( arguments[0] ) + strlen( currdir ) + 1 );
					strcpy( newarg, currdir );
					if(currdir[currdir_len-1] != '/')
						strcat( newarg, "/" );
					strcat( newarg, arguments[0] );
					free( arguments[0] );
					arguments[0] = newarg;
				}
				
				// probably is runnable
				runnable = 1;
			}
		}
		else
			runnable = 1; // direct path to a binary
		
		// check that we can run the program
		if( runnable )
		{
			// safe to run the program
#if CANFORK
			int p = fork();
			if(p == 0)
			{
#endif
				arguments[argcount] = 0;
				int newid = execve( arguments[0], arguments, 0 );
				
				if( newid == -1 )
					printf( "minish: command '%s' not found! Check that there is no typing error and that the file exists.\n", arguments[0] );
				else
					asm volatile( "int $0x80" : : "a" (16), "b" (newid) );

#if CANFORK					
				kill( getpid() );
				while( 1 );
			}
			else
			{
				if(p != -1)
					asm volatile( "int $0x80" : : "a" (16), "b" (p) );
			}
#endif
		}
		
		// free all used arguments
		for( i = 0; i < argcount; i++ )
			free( arguments[i] );
	}
	
	// we died!
	printf( "MiniSH: FATAL: fell out of the main loop!\n" );
	
	// end the process
	kill( getpid() );
	while( 1 );
}
